假設你今天不是在寫一個聊天機器人,而是在替 OpenClaw 裝一扇新門。
這扇門剛好開在 Telegram 上。
有人從 Telegram 傳訊息進來,OpenClaw 要認得他、知道他屬於哪個 agent、要不要放行、要不要綁定 session,最後還要把回覆送回 Telegram。
如果只用「讀到訊息就回一句」的思維,很容易把整件事想得太小。
真正麻煩的是:
所以第 21 天我要看的不是「Telegram 能不能收訊息」,而是:
Telegram plugin 到底怎麼接進 OpenClaw 的核心?
這一篇是後面 Telegram 訊息流的前奏。
先把門框裝好,才有資格談門裡面怎麼走。
Telegram plugin 的接法其實可以拆成兩層。
第一層是「外殼」:
extensions/telegram/index.ts
defineBundledChannelEntry(...)
這一層負責告訴 OpenClaw:
第二層是「核心設定」:
extensions/telegram/src/channel.ts
createChatChannelPlugin(...)
這一層負責告訴 OpenClaw:
換句話說:
index.ts 像是把 Telegram 插件插上電channel.ts 才是 Telegram 跟 OpenClaw 對接的轉接頭先看最外層的入口。
📄 原始碼:
extensions/telegram/index.ts:2-20
import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract";
export default defineBundledChannelEntry({
id: "telegram",
name: "Telegram",
description: "Telegram channel plugin",
importMetaUrl: import.meta.url,
plugin: {
specifier: "./channel-plugin-api.js",
exportName: "telegramPlugin",
},
secrets: {
specifier: "./secret-contract-api.js",
exportName: "channelSecrets",
},
runtime: {
specifier: "./runtime-api.js",
exportName: "setTelegramRuntime",
},
});
這段很像是在報戶口。
它沒有真的開始收訊息,也沒有真的打 Telegram API。
它只是把 Telegram plugin 的身分、入口、secret、runtime 告訴 OpenClaw。
再看真正的核心對接。
📄 原始碼:
extensions/telegram/src/channel.ts:730-922
export const telegramPlugin = createChatChannelPlugin({
base: {
...createTelegramPluginBase({
setupWizard: telegramSetupWizard,
setup: telegramSetupAdapter,
}),
allowlist: buildDmGroupAccountAllowlistAdapter({
channelId: "telegram",
resolveAccount: resolveTelegramAccount,
normalize: ({ cfg, accountId, values }) =>
telegramConfigAdapter.formatAllowFrom!({ cfg, accountId, allowFrom: values }),
resolveDmAllowFrom: (account) => account.config.allowFrom,
resolveGroupAllowFrom: (account) => account.config.groupAllowFrom,
resolveDmPolicy: (account) => account.config.dmPolicy,
resolveGroupPolicy: (account) => account.config.groupPolicy,
resolveGroupOverrides: resolveTelegramAllowlistGroupOverrides,
}),
bindings: {
selfParentConversationByDefault: true,
compileConfiguredBinding: ({ conversationId }) =>
normalizeTelegramAcpConversationId(conversationId),
matchInboundConversation: ({ compiledBinding, conversationId, parentConversationId }) =>
matchTelegramAcpConversation({
bindingConversationId: compiledBinding.conversationId,
conversationId,
parentConversationId,
}),
resolveCommandConversation: ({ threadId, originatingTo, commandTo, fallbackTo }) =>
resolveTelegramCommandConversation({
threadId,
originatingTo,
commandTo,
fallbackTo,
}),
},
messaging: {
normalizeTarget: normalizeTelegramMessagingTarget,
resolveInboundConversation: ({ to, conversationId, threadId }) =>
resolveTelegramInboundConversation({ to, conversationId, threadId }),
resolveDeliveryTarget: ({ conversationId, parentConversationId }) =>
resolveTelegramDeliveryTarget({ conversationId, parentConversationId }),
resolveSessionConversation: ({ kind, rawId }) => resolveTelegramSessionConversation({ kind, rawId }),
parseExplicitTarget: ({ raw }) => parseTelegramExplicitTarget(raw),
inferTargetChatType: ({ to }) => parseTelegramExplicitTarget(to).chatType,
},
setup: telegramSetupAdapter,
status: createComputedAccountStatusAdapter<ResolvedTelegramAccount, TelegramProbe>({
/* ... */
}),
},
});
這段的重點不是每個 helper 的名字,而是它把 Telegram 對接成一個完整 channel。
OpenClaw 不是只要知道「這裡是 Telegram」就夠了。
它還要知道:
defineBundledChannelEntry 是把插件接到 loader這個函式像是一個包裝器。
你可以把它想成:
它要回報給 runtime:
所以 Telegram plugin 不是「寫好就算」。
它必須先進入 OpenClaw 的插件契約。
createChatChannelPlugin 才是核心裝配這個工廠函式把 Telegram 包成一個真正的 channel plugin。
裡面每個區塊都不是多餘的:
base:基本能力和 setupallowlist:誰可以進來bindings:conversation 怎麼綁 sessionmessaging:target 怎麼解、回覆怎麼送setup:怎麼安裝、怎麼填 tokenstatus:怎麼檢查狀態換句話說,Telegram 不是一個孤零零的 webhook。
它是一整組可以被 OpenClaw core 管理的能力集合。
真正的業務本體其實在 OpenClaw core:
Telegram plugin 只是把這些核心能力套到 Telegram 的語境裡。
所以你會看到它一直在翻譯:
它不是在重新發明一套 agent system。
它是在把 Telegram 世界翻成 OpenClaw 可以理解的世界。
如果把 Telegram 邏輯全部塞進 core,core 會變得很髒。
如果把 core 邏輯全部塞進 Telegram plugin,每個 channel 又會各寫一份。
OpenClaw 的做法比較像:
這樣 Telegram、LINE、Discord、Slack 才能共用同一套心臟。
但這種複雜度是值得的。
因為 Telegram 只是第一個 plugin,真正重要的是這個框架以後能不能接更多 channel。
index.ts 負責讓 loader 找到 plugin,channel.ts 負責把 Telegram 裝進 core第 22 天我會接著看 Telegram 的訊息流。
入口裝好了之後,訊息怎麼進、怎麼驗證、怎麼進到 reply pipeline,才是最有戲的地方。